package be.rivendale.ghetto;

import be.rivendale.geometry.AxisAlignedBoundingBox;
import be.rivendale.geometry.TriangleModel;
import be.rivendale.material.Color;
import be.rivendale.mathematics.Point;
import be.rivendale.mathematics.Triangle;
import be.rivendale.mathematics.Triple;

import javax.swing.*;

public class VoxelizerMarkThree {
    public VoxelModel voxelize(TriangleModel triangleModel, Triple voxelSize, Point minimumBound) {
        AxisAlignedBoundingBox triangleModelBoundingBox = triangleModel.getAxisAlignedBoundingBox();
        System.out.format("Source model for voxelization: (width=%f, height=%f, depth=%f), minimum bound: (x=%f, y=%f, z=%f)%n",
                triangleModelBoundingBox.getWidth(),
                triangleModelBoundingBox.getHeight(),
                triangleModelBoundingBox.getDepth(),
                triangleModelBoundingBox.getMinimumBound().getX(),
                triangleModelBoundingBox.getMinimumBound().getY(),
                triangleModelBoundingBox.getMinimumBound().getZ()
        );

        int xResolution = (int) Math.ceil(triangleModelBoundingBox.getWidth() / voxelSize.getX());
        int yResolution = (int) Math.ceil(triangleModelBoundingBox.getHeight() / voxelSize.getY());
        int zResolution = (int) Math.ceil(triangleModelBoundingBox.getDepth() / voxelSize.getZ());


        AxisAlignedBoundingBox voxelModelBoundingBox = createVoxelModelBoundingBox(triangleModelBoundingBox.getMinimumBound(), voxelSize, xResolution, yResolution, zResolution);
        System.out.format("Destination model for voxelization: (x: %d, y: %d, z: %d, t: %d), voxel model dimensions (width=%f, height=%f, depth=%f)%n",
                xResolution,
                yResolution,
                zResolution,
                xResolution * yResolution * zResolution,
                voxelModelBoundingBox.getWidth(),
                voxelModelBoundingBox.getHeight(),
                voxelModelBoundingBox.getDepth()
        );

        Voxel[][][] voxels = new Voxel[xResolution][yResolution][zResolution];

        ProgressMonitor monitor = new ProgressMonitor(null, "Voxelizing", null, 0, triangleModel.getTriangles().size() - 1);
        for(int triangleIndex = 0; triangleIndex < triangleModel.getTriangles().size(); triangleIndex++) {
            monitor.setProgress(triangleIndex);
            Triangle triangle = triangleModel.getTriangles().get(triangleIndex);

            //System.out.println("Processing triangle");

            // Try to filter out as many voxels as possible before iterating over each for a voxel-triangle intersection test.
            // We achieve this by calculating the minimum and maximum voxel indices for each triangle's bounding box, so we end up with
            // a range of voxels that are candidates to have an intersection with the triangle.
            AxisAlignedBoundingBox triangleBoundingBox = triangle.getAxisAlignedBoundingBox();

            Point minimumBoundDelta = triangleBoundingBox.getMinimumBound().subtract(voxelModelBoundingBox.getMinimumBound());
            int minimumVoxelIndexX = (int) Math.floor(minimumBoundDelta.getX() / voxelSize.getX());
            int minimumVoxelIndexY = (int) Math.floor(minimumBoundDelta.getY() / voxelSize.getY());
            int minimumVoxelIndexZ = (int) Math.floor(minimumBoundDelta.getZ() / voxelSize.getZ());

            Point maximumBoundDelta = triangleBoundingBox.getMaximumBound().subtract(voxelModelBoundingBox.getMinimumBound());
            int maximumVoxelIndexX = (int) Math.ceil(maximumBoundDelta.getX() / voxelSize.getX());
            int maximumVoxelIndexY = (int) Math.ceil(maximumBoundDelta.getY() / voxelSize.getY());
            int maximumVoxelIndexZ = (int) Math.ceil(maximumBoundDelta.getZ() / voxelSize.getZ());
//            System.out.format("Voxel index range for triangle = [%d, %d, %d] -> [%d, %d, %d] %n",
//                    minimumVoxelIndexX,
//                    minimumVoxelIndexY,
//                    minimumVoxelIndexZ,
//                    maximumVoxelIndexX,
//                    maximumVoxelIndexY,
//                    maximumVoxelIndexZ
//            );

            // Iterate over the voxel ranges
            for(int zIndex = minimumVoxelIndexZ; zIndex < maximumVoxelIndexZ; zIndex++) {
                for(int yIndex = minimumVoxelIndexY; yIndex < maximumVoxelIndexY; yIndex++) {
                    for(int xIndex = minimumVoxelIndexX; xIndex < maximumVoxelIndexX; xIndex++) {
                        AxisAlignedBoundingBox voxelBoundingBox = buildVoxelBoundingBox(voxelModelBoundingBox, xIndex, yIndex, zIndex, voxelSize);
                        if (AabbTriangleIntersectionGhetto.intersects(voxelBoundingBox, triangle)) {
                            Voxel voxel = new Voxel(assignRandomColor(), triangle.normal().normalize().invert());
                            voxels[xIndex][yIndex][zIndex] = voxel;
                        }
                    }
                }
            }
        }

        // Wrap and return as voxel model
        return new VoxelModel(minimumBound, voxels, voxelModelBoundingBox.getWidth(), voxelModelBoundingBox.getHeight(), voxelModelBoundingBox.getDepth());
    }

    private AxisAlignedBoundingBox createVoxelModelBoundingBox(Point minimumBound, Triple voxelSize, int xResolution, int yResolution, int zResolution) {
        // Determine the voxel dimensions
        Triple voxelModelDimensions = new Triple(xResolution * voxelSize.getX(),
                yResolution * voxelSize.getY(),
                zResolution * voxelSize.getZ()
        );
        return new AxisAlignedBoundingBox(minimumBound, minimumBound.add(voxelModelDimensions));
    }

    private static Color[] availableColors = {Color.RED, Color.GREEN, Color.BLUE, Color.CYAN, Color.MAGENTA, Color.YELLOW};

    private Color assignRandomColor() {
        int colorIndex = (int) (Math.random() * availableColors.length);
        return availableColors[colorIndex];
    }

    private AxisAlignedBoundingBox buildVoxelBoundingBox(AxisAlignedBoundingBox voxelModelBoundingBox, int x, int y, int z, Triple voxelSize) {
        Point minimumBound = voxelModelBoundingBox.getMinimumBound().add(
                new Triple(voxelSize.getX() * x, voxelSize.getY() * y, voxelSize.getZ() * z));
        Point maximumBound = minimumBound.add(voxelSize);

        return new AxisAlignedBoundingBox(minimumBound, maximumBound);
    }
}
